package net.alche.fa.core.model;

import java.util.Date;
import net.alche.fa.core.SageStone;
import net.alche.fa.core.FaException;
import java.util.Map;
import java.util.List;
import net.alche.al.util.AC;
import java.util.ArrayList;
import java.util.HashMap;
import java.lang.reflect.Array;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

import net.alche.fa.core.FaPlatform;
import net.alche.fa.core.CodeLevel;
import java.util.Map.Entry;

//
// OValue
//
@SuppressWarnings("all")
public class FaValue {
  public static final char _BYTE = 'y';
  public static final char _INT = 'i';
  public static final char _LONG = 'g';
  public static final char _CHAR = 'c';
  public static final char _DOUBLE = 'd';
  public static final char _BOOL = 'b';
  public static final char _STRING = 's';
  public static final char _DATE = 't';
  public static final char _ARR = 'r';
  public static final char _MAP = 'm';
  public static final char _LIST = 'l';
  public static final char _EXCEPTION = 'e';
  public static final char _OBJECT = 'o';
  public static final char _SAGE = 'a';
  public static final char _NULL = 'N';
  private boolean isNull;
  private String varName;
  public char type = _NULL;
  public String objType;
  private Boolean boolValue;
  private Character charValue;
  private Integer intValue;
  private Double doubleValue;
  private Byte byteValue;
  private Long longValue;
  private Date dateValue;
  private String strValue;
  // @JsonIgnore
  // private FaValue[] arrValue
  // @JsonIgnore
  // private Map<String, FaValue> mapValue
  // @JsonIgnore
  // private List<FaValue> listValue
  private Object objValue;
  private transient SageStone sageValue;
  private transient FaException exValue;
  private transient FaValue parObj; // 空值赋值时,父对象/数组
  private Integer elIndex; // 父数组下标
  private String elAttr; // 子父对象的属性

  public FaValue() {
    setNull(true);
  }

  public FaValue(boolean isNull, String varName) {
    setNull(true);
    this.varName = varName;
  }

  public FaValue(Object data) {
    aiData(data);
  }

  public FaValue(Object data, char type) {
    this.type = type;
    parseData(data);
    this.objType = FaValue.getCharStr(type);
  }

  public FaValue aiData(Object data) {
    if (data == null) {
      this.isNull = true;
      return this;
    }
    if (data instanceof Integer) {
      type = _INT;
      this.objType = "int";
    } else if (data instanceof Double) {
      type = _DOUBLE;
      this.objType = "double";
    } else if (data instanceof Date) {
      type = _DATE;
      this.objType = "Date";
    } else if (data instanceof Character) {
      type = _CHAR;
      this.objType = "char";
    } else if (data instanceof Byte) {
      type = _BYTE;
      this.objType = "byte";
    } else if (data instanceof Long) {
      type = _LONG;
      this.objType = "long";
    } else if (data.getClass().isArray()) {
      type = _ARR;
      this.objType = "FxArray";
    } else if (data instanceof Map) {
      type = _MAP;
      this.objType = "Map";
      this.objValue = data;
    } else if (data instanceof List) {
      type = _LIST;
      this.objType = "List";
      this.objValue = data;
    } else if (data instanceof Boolean) {
      type = _BOOL;
      this.objType = "boolean";
    } else if (data instanceof String) {
      type = _STRING;
      this.objType = "String";
    } else if (data instanceof SageStone) {
      type = _SAGE;
      this.objType = ((SageStone) data).getFaClass().getConf().getName();
    } else if (data instanceof FaException) {
      type = _EXCEPTION;
      this.objType = "Exception";
    } else if (data instanceof FaValue) {
      FaValue dataVal = (FaValue) data;
      this.sageValue = dataVal.sageValue;
      this.type = dataVal.getType();
      this.isNull = dataVal.isNull;
      return this;
    } else {
      type = _OBJECT;
      this.objType = "Object";
      if (data != null) {
        objType = data.getClass().getSimpleName();
      }
    }
    parseData(data);
    return this;
  }

  private void parseData(Object data) {
    if (data == null) {
      this.isNull = true;
      return;
    }
    this.isNull = false;
    switch (type) {
      case _INT: {
        String str = AC.toString(data);
        if (AC.isEmpty(str)) {
          this.isNull = true;
        } else {
          intValue = (data instanceof Integer) ? (Integer) data : (int) Double.parseDouble(str);
        }
        break;
      }
      case _BYTE: {
        String str = AC.toString(data);
        if (AC.isEmpty(str)) {
          this.isNull = true;
        } else {
          byteValue = (data instanceof Byte) ? (Byte) data : Byte.parseByte(str);
        }
        break;
      }
      case _BOOL: {
        String str = AC.toString(data);
        if (AC.isEmpty(str)) {
          this.isNull = true;
        } else {
          boolValue = (data instanceof Boolean) ? (Boolean) data : Boolean.parseBoolean(str);
        }
        break;
      }
      case _DOUBLE: {
        String str = AC.toString(data);
        if (AC.isEmpty(str)) {
          this.isNull = true;
        } else {
          doubleValue = (data instanceof Double) ? (Double) data : Double.parseDouble(str);
        }
        break;
      }
      case _LONG: {
        String str = AC.toString(data);
        if (AC.isEmpty(str)) {
          this.isNull = true;
        } else {
          longValue = (data instanceof Long) ? (Long) data : Long.parseLong(str);
        }
        break;
      }
      case _DATE: {
        if (data instanceof LocalDateTime ldt) {
          Instant instant = ldt.atZone(ZoneId.systemDefault()).toInstant();
          dateValue = Date.from(instant);
        } else {
          String str = AC.toString(data);
          if (AC.isEmpty(str)) {
            this.isNull = true;
          } else {
            dateValue = (data instanceof Date) ? (Date) data : AC.date.parse(str, null);
          }
        }
        break;
      }
      case _CHAR: {
        charValue = (data instanceof Character) ? (Character) data : (char) data;
        break;
      }
      case _SAGE: {
        this.sageValue = (SageStone) data;
        break;
      }
      case _ARR: {
        this.objValue = data;
        // parseArr(data)
        break;
      }
      case _MAP: {
        // parseMap(data)
        this.objValue = data;
        break;
      }
      case _LIST: {
        this.objValue = data;
        // parseList(data)
        break;
      }
      case _OBJECT: {
        objValue = data;
        break;
      }
      case _EXCEPTION: {
        exValue = (FaException) data;
        break;
      }
      default: {
        strValue = AC.toString(data);
      }
    }
  }

  public Class getValueClass() {
    switch (type) {
      case _INT: {
        return Integer.class;
      }
      case _BOOL: {
        return Boolean.class;
      }
      case _DOUBLE: {
        return Double.class;
      }
      case _BYTE: {
        return Byte.class;
      }
      case _LONG: {
        return Long.class;
      }
      case _CHAR: {
        return Character.class;
      }
      case _DATE: {
        return Date.class;
      }
      case _ARR: {
        return Object[].class;
      }
      case _MAP: {
        return Map.class;
      }
      case _LIST: {
        return List.class;
      }
      case _EXCEPTION: {
        return Exception.class;
      }
      case _OBJECT: {
        if (objValue == null) {
          return Object.class;
        }
        return objValue.getClass();
      }
      case _SAGE: {
        return SageStone.class;
      }
      default: {
        return String.class;
      }
    }
  }

  public Object getJava() {
    switch (type) {
      case _MAP: {
        // return toMap(this)
        return this.parseMapToJava(this.objValue);
      }
      case _LIST: {
        // return toList(this)
        return this.parseListToJava(this.objValue);
      }
      case _ARR: {
        // return toArr(this)
        return this.parseArrayToJava(this.objValue);
      }
      case _SAGE: {
        if (sageValue == null) {
          return null;
        }
        Map<String, Object> map = sageValue.toJavaMap();
        return map;
      }
    }
    if (type == _OBJECT && this.objValue != null) {
      // if ( this.objValue instanceof PageList) {
      // PageList pageList = (PageList)this.objValue;
      // if ( pageList.getList() != null) {
      // Object list = this.parseListToJava(pageList.getList());
      // pageList.setList((List)list);
      // }
      // }
    }
    Object obj = getValue();
    return obj;
  }

  public Object getValue() {
    switch (type) {
      case _BOOL: {
        return boolValue;
      }
      case _STRING: {
        return strValue;
      }
      case _INT: {
        return intValue;
      }
      case _DOUBLE: {
        return doubleValue;
      }
      case _BYTE: {
        return byteValue;
      }
      case _LONG: {
        return longValue;
      }
      case _DATE: {
        return dateValue;
      }
      case _CHAR: {
        return charValue;
      }
      case _MAP: {
        return this.objValue;
      }
      case _LIST: {
        return this.objValue;
      }
      case _ARR: {
        return this.objValue;
      }
      case _OBJECT: {
        return objValue;
      }
      case _EXCEPTION: {
        return exValue;
      }
      case _SAGE: {
        return this.sageValue;
      }
      default: {
        return strValue;
      }
    }
  }

  Object parseListToJava(Object listObj) {
    Object obj = listObj;
    if (listObj == null) {
      return obj;
    }
    List list = (List) listObj;
    if (list.size() > 0) {
      Object item = list.get(0);
      if (item instanceof FaValue) {
        List newList = new ArrayList();
        for (Object sub : list) {
          FaValue fav = (FaValue) sub;
          newList.add(fav.getJava());
        }
        return newList;
      }
      if (item instanceof SageStone) {
        List newList = new ArrayList();
        for (Object sub : list) {
          SageStone sage = (SageStone) sub;
          newList.add(sage.toJavaMap());
        }
        return newList;
      }
    }
    return obj;
  }

  Object parseMapToJava(Object mapObj) {
    Object obj = mapObj;
    if (mapObj == null) {
      return obj;
    }
    Map map = (Map) mapObj;
    if (!map.isEmpty()) {
      boolean isFirst = true;
      Map newMap = new HashMap();
      boolean isList = false;
      for (Object sub : map.keySet()) {
        isList = false;
        Object val = map.get(sub);
        if (val == null) {
          continue;
        }
        if (val instanceof SageStone) {
          SageStone sageValue = (SageStone) val;
          newMap.put(sub, sageValue.toJavaMap());
        } else if (val instanceof List) {
          newMap.put(sub, this.parseListToJava(val));
        } else if (val instanceof Map) {
          newMap.put(sub, this.parseMapToJava(val));
        } else {
          newMap.put(sub, val);
        }
      }
      if (newMap != null) {
        return newMap;
      }
    }
    return obj;
  }

  Object parseArrayToJava(Object arrObj) {
    Object obj = arrObj;
    if (arrObj == null) {
      return obj;
    }
    int len = Array.getLength(arrObj);
    if (len > 0) {
      Object firstObj = Array.get(arrObj, 0);
      if (firstObj instanceof FaValue) {
        Object newArr = Array.newInstance(Object.class, len);
        for (int i = 0; i < len; i++) {
          FaValue elObj = (FaValue) Array.get(arrObj, i);
          if (elObj != null) {
            Array.set(newArr, i, elObj.getJava());
          }
        }
        return newArr;
      }
    }
    return obj;
  }

  public static FaValue parseValue(String value) throws Exception {
    try {
      int index = value.indexOf("|");
      char ct = _STRING;
      if (index > 0) {
        String dataType = value.substring(0, index);
        value = value.substring(index + 1, value.length());
        ct = getChar(dataType);
      }
      return new FaValue(value, ct);
    } catch (StringIndexOutOfBoundsException ex) {
      throw new Exception("原力对象parseValue[" + value + "]缺少符号\"|\"");
    }
  }

  public void giveValue(FaValue fv, FaPlatform plat) {
    FaValue val = this;
    val.setStrValue(null);
    val.setCharValue(null);
    val.setIntValue(null);
    val.setDoubleValue(null);
    val.setDateValue(null);
    val.setBoolValue(null);
    val.setByteValue(null);
    val.setLongValue(null);
    // val.setArrValue(null)
    // val.setListValue(null)
    // val.setMapValue(null)
    val.setObjValue(null);
    val.setSageValue(null);
    val.setExValue(null);
    if (fv != null && !fv.isNull()) {
      switch (val.getType()) {
        case FaValue._INT: {
          val.setIntValue(fv.getIntValue());
          break;
        }
        // case FaValue._LONG
        // val.setLongValue(fv.getLongValue())
        // break
        case FaValue._DOUBLE: {
          val.setDoubleValue(fv.getDoubleValue());
          break;
        }
        case FaValue._STRING: {
          val.setStrValue(fv.getStrValue());
          break;
        }
        case FaValue._BOOL: {
          val.setBoolValue(fv.getBoolValue());
          break;
        }
        case FaValue._CHAR: {
          val.setCharValue(fv.getCharValue());
          break;
        }
        case FaValue._DATE: {
          val.setDateValue(fv.getDateValue());
          break;
        }
        // case FaValue._ARR
        // val.setArrValue(fv.getArrValue())
        // break
        // case FaValue._MAP
        // val.setMapValue(fv.getMapValue())
        // break
        // case FaValue._LIST
        // val.setListValue(fv.getListValue())
        // break
        case FaValue._SAGE: {
          val.setSageValue(fv.getSageValue());
          break;
        }
        case FaValue._EXCEPTION: {
          val.setExValue(fv.getExValue());
          break;
        }
        case FaValue._OBJECT: {
          val.setObjValue(fv.getValue());
          break;
        }
        case FaValue._BYTE: {
          val.setByteValue(fv.getByteValue());
          break;
        }
        case FaValue._LONG: {
          val.setLongValue(fv.getLongValue());
          break;
        }
      }
      // val.setType(fv.getType())
      val.setNull(false);
      if (val.type != FaValue._OBJECT) {
        val.setObjValue(fv.getObjValue());
      }
      if (val.parObj != null) {
        if (val.getElIndex() != null) {
          String sageName = plat.getInnterType(val.getParObj().objType);
          if (plat.isSageName(sageName)) {
            Array.set(val.getParObj().getObjValue(), val.getElIndex(), val);
          } else {
            Array.set(val.getParObj().getObjValue(), val.getElIndex(), val.getValue());
          }
        }
      }
      // AC.log(val.parObj)
      if (val.getType() == FaValue._OBJECT && fv.getSageValue() != null) {
        val.setObjValue(null);
        val.setSageValue(fv.getSageValue());
        val.setType(FaValue._SAGE);
        val.setObjType(fv.getObjType());
      }
    } else {
      val.setNull(true);
    }
  }

  public FaValue newValue() throws FaException {
    FaValue ov = new FaValue();
    ov.isNull = this.isNull;
    ov.setType(this.getType());
    ov.setBoolValue(this.boolValue);
    ov.setDateValue(this.dateValue);
    ov.setDoubleValue(this.doubleValue);
    ov.setStrValue(this.strValue);
    ov.setIntValue(this.intValue);
    ov.setCharValue(this.charValue);
    ov.setByteValue(this.byteValue);
    ov.setLongValue(this.longValue);
    // ov.setMapValue(this.mapValue)
    ov.setObjValue(this.objValue);
    // ov.setListValue(this.listValue)
    ov.setSageValue(this.sageValue);
    // ov.setArrValue(this.arrValue)
    return ov;
  }

  // public static FaValue newValue(String typeStr, SageStone one) throws
  // Exception
  // FaValue ov = new FaValue()
  // ov.isNull = false
  // char type = getChar(typeStr)
  // if type == _DATE
  // ov.setDateValue(new Date())
  // if type == _LIST
  // ov.setListValue(new ArrayList<>())
  // if type == _MAP
  // ov.setMapValue(new HashMap<>())
  // return ov
  public static String getCharStr(char type) {
    switch (type) {
      case _STRING: {
        return "String";
      }
      case _BOOL: {
        return "bool";
      }
      case _INT: {
        return "int";
      }
      case _CHAR: {
        return "char";
      }
      case _DOUBLE: {
        return "double";
      }
      case _BYTE: {
        return "byte";
      }
      case _LONG: {
        return "long";
      }
      case _DATE: {
        return "Date";
      }
      case _ARR: {
        return "[]";
      }
      case _MAP: {
        return "Map";
      }
      case _LIST: {
        return "List";
      }
      case _OBJECT: {
        return "Object";
      }
      case _EXCEPTION: {
        return "Exception";
      }
      default: {
        return "Object";
      }
    }
  }

  public static char getChar(String strType) {
    if (strType.length() == 1) {
      return strType.charAt(0);
    }
    switch (strType) {
      case "String": {
        return _STRING;
      }
      case "boolean": {
        return _BOOL;
      }
      case "bool": {
        return _BOOL;
      }
      case "Boolean": {
        return _BOOL;
      }
      case "int": {
        return _INT;
      }
      case "Integer": {
        return _INT;
      }
      case "double": {
        return _DOUBLE;
      }
      case "Double": {
        return _DOUBLE;
      }
      case "char": {
        return _CHAR;
      }
      case "Character": {
        return _CHAR;
      }
      case "byte": {
        return _BYTE;
      }
      case "Byte": {
        return _BYTE;
      }
      case "long": {
        return _LONG;
      }
      case "Long": {
        return _LONG;
      }
      case "Date": {
        return _DATE;
      }
      case "[]": {
        return _ARR;
      }
      case "Map": {
        return _MAP;
      }
      case "List": {
        return _LIST;
      }
      case "Object": {
        return _OBJECT;
      }
      case "Exception": {
        return _EXCEPTION;
      }
      default: {
        return _OBJECT;
      }
    }
  }

  public boolean isNull() {
    return this.isNull;
  }

  public void setNull(boolean isNull) {
    this.isNull = isNull;
  }

  public static FaValue instance(String str) {
    FaValue ov = new FaValue();
    if (str.startsWith("''") && str.endsWith("'")) {
      ov.strValue = str.substring(1, str.length());
      ov.type = _STRING;
    } else if (str.startsWith("{") && str.endsWith("}")) {
      ov.strValue = str.substring(1, str.length());
      ov.type = _MAP;
    } else if (str.startsWith("[") && str.endsWith("]")) {
      ov.strValue = str.substring(1, str.length());
      ov.type = _LIST;
    } else if (str.contains(".")) {
      ov.type = _DOUBLE;
    }
    return ov;
  }

  public Integer getIntValue() {
    if (type == _DOUBLE) {
      return doubleValue != null ? doubleValue.intValue() : null;
    } else if (type == _STRING) {
      return Integer.parseInt(strValue);
    } else if (type == _CHAR) {
      return (int) charValue.charValue();
    } else if (type == _BYTE) {
      return (int) byteValue;
    } else if (type == _LONG) {
      return (int) longValue.intValue();
    } else if (type == _OBJECT) {
      return (Integer) this.objValue;
    }
    return intValue;
  }

  public void setIntValue(Integer intValue) {
    this.intValue = intValue;
  }

  public Double getDoubleValue() {
    if (type == _INT) {
      return intValue != null ? (double) intValue : null;
    } else if (type == _STRING) {
      return Double.parseDouble(strValue);
    }
    return doubleValue;
  }

  public Boolean getBoolValue() {
    if (type == _INT) {
      return intValue == 1 ? true : false;
    } else if (type == _STRING) {
      return Boolean.parseBoolean(strValue);
    } else if (this.objValue != null) {
      return (Boolean) this.objValue;
    }
    return boolValue;
  }

  public String getStrValue() {
    if (type != _STRING) {
      return AC.toString(getValue());
    }
    return strValue;
  }

  public void setDoubleValue(Double doubleValue) {
    this.doubleValue = doubleValue;
  }

  public Date getDateValue() {
    return dateValue;
  }

  public void setDateValue(Date dateValue) {
    this.dateValue = dateValue;
  }

  public void setStrValue(String strValue) {
    this.strValue = strValue;
  }

  public char getType() {
    return type;
  }

  public void setType(char type) {
    this.type = type;
  }

  // public Map<String,FaValue> getMapValue()
  // return mapValue
  //
  // public void setMapValue(Map<String, FaValue> mapValue)
  // this.mapValue = mapValue
  // public List<FaValue> getListValue()
  // if listValue != null
  // return listValue
  // if type == _OBJECT && objValue != null
  // parseList(objValue)
  //
  // return listValue
  //
  // public void setListValue(List<FaValue> listValue)
  // this.listValue = listValue
  public Object getObjValue() {
    return objValue;
  }

  public void setObjValue(Object objValue) {
    this.objValue = objValue;
  }

  public void setBoolValue(Boolean v) {
    this.boolValue = v;
  }

  public SageStone getSageValue() {
    return sageValue;
  }

  public void setSageValue(SageStone sageValue) {
    this.sageValue = sageValue;
  }

  public String getObjType() {
    if (objType == null && this.sageValue != null) {
      return this.sageValue.getFaClassName();
    }
    return objType;
  }

  public void setObjType(String objType) {
    this.objType = objType;
  }

  public FaException getExValue() {
    return exValue;
  }

  public void setExValue(FaException exValue) {
    this.exValue = exValue;
  }

  public String getVarName() {
    return this.varName;
  }

  public void setVarName(String varName) {
    this.varName = varName;
  }

  public Character getCharValue() {
    if (type == _INT) {
      return (char) intValue.intValue();
    } else if (type == _OBJECT) {
      return (char) objValue;
    }
    return charValue;
  }

  public void setCharValue(Character charValue) {
    this.charValue = charValue;
  }

  // public FaValue[] getArrValue()
  // return arrValue
  //
  // public void setArrValue(FaValue[] arrValue)
  // this.arrValue = arrValue
  public Byte getByteValue() {
    if (type == _INT) {
      return intValue.byteValue();
    } else if (type == _CHAR) {
      return (byte) ((int) charValue);
    }
    return byteValue;
  }

  public void setByteValue(Byte byteValue) {
    this.byteValue = byteValue;
  }

  public Long getLongValue() {
    if (type == _INT) {
      return (long) intValue;
    } else if (type == _CHAR) {
      return (long) charValue;
    } else if (type == _BYTE) {
      return (long) byteValue;
    }
    return longValue;
  }

  public void setLongValue(Long longValue) {
    this.longValue = longValue;
  }

  public static List<FaValue> toFaList(FaPlatform plat, List<Map> list, String pojoName, CodeLevel lvl)
      throws FaException {
    if (list == null) {
      return null;
    }
    List<FaValue> fvList = new ArrayList<>();
    if (list != null && list.size() > 0) {
      for (Map item : list) {
        FaValue val = FaValue.mapToPojo(plat, item, pojoName, lvl);
        fvList.add(val);
      }
    }
    return fvList;
  }

  public static FaValue mapToPojo(FaPlatform plat, Map<String, Object> map, String pojoName, CodeLevel lvl)
      throws FaException {
    if ("Map".equals(pojoName)) {
      return new FaValue(map);
    } else {
      SageStone st = plat.instance(pojoName, lvl).getThisSage();
      FaValue fav = new FaValue(st);
      fav.setObjType(pojoName);
      if (map != null && !map.isEmpty()) {
        for (Entry<String, Object> en : map.entrySet()) {
          String attrName = AC.str.getHump(en.getKey());
          FaValue varInfo = st.getVars().get(attrName);
          if (varInfo == null) {
            continue;
          }
          Object objValue = en.getValue();
          if (objValue == null) {
            continue;
          }
          // AC.log( plat.getInnterType(varInfo.getObjType()), varInfo.getObjType())
          String innerType = plat.getInnterType(varInfo.getObjType());
          if (plat.isSageName(innerType)) {
            if (varInfo.getObjType().startsWith("List")) {
              if (objValue instanceof List) {
                List<Map> subMapList = (List) objValue;
                FaValue val = new FaValue();
                val.setType(FaValue._LIST);
                val.setNull(false);
                List<FaValue> listVal = new ArrayList<>();
                val.setObjValue(listVal);
                val.setObjType(varInfo.getObjType());
                for (Map sub : subMapList) {
                  FaValue subVal = mapToPojo(plat, sub, innerType, lvl);
                  listVal.add(subVal);
                }
                st.putVar(attrName, val);
              }
            }
            // AC.log(attrName)
            else if (objValue instanceof Map) {
              FaValue val = mapToPojo(plat, (Map) objValue, varInfo.getObjType(), lvl);
              st.putVar(attrName, val);
            }
          } else {
            st.putVar(attrName, new FaValue(objValue));
          }
        }
      }
      return fav;
    }
  }

  public FaValue getParObj() {
    return this.parObj;
  }

  public void setParObj(FaValue parObj) {
    this.parObj = parObj;
  }

  public Integer getElIndex() {
    return this.elIndex;
  }

  public void setElIndex(Integer elIndex) {
    this.elIndex = elIndex;
  }

  public String getElAttr() {
    return this.elAttr;
  }

  public void setElAttr(String elAttr) {
    this.elAttr = elAttr;
  }
}